#include "xthread-sema.h"
#include "inneral/xthread.h"
#include <errno.h>
#include <stdio.h>

static void xthread_semaphore_wait_inneral(xthread_sema_t *sema)
{
	list_add_tail(&xthread_current->list, &sema->waiters.wait_list);
    xthread_mutex_unlock(&sema->lock);
	xthread_block(XTHREAD_BLOCK);
}

static void xthread_sema_post_inneral(xthread_sema_t *sema)
{
	xthread_struct_t *waiter = list_first_owner_or_null(&sema->waiters.wait_list, xthread_struct_t, list);
	if (waiter) {
        list_del(&waiter->list);
        // unblock thread untill success
        while (xthread_unblock(waiter) == -1);
    }
    xthread_mutex_unlock(&sema->lock);
}

void xthread_sema_wait(xthread_sema_t *sema)
{
    XPRINT("sema wait %d\n", xthread_current->tid);
    xthread_mutex_lock(&sema->lock);
    if (sema->counter > 0) {
        XPRINT("sema wait %d ok\n", xthread_current->tid);
        sema->counter--;
        xthread_mutex_unlock(&sema->lock);
        return;
    } else {
        XPRINT("sema wait %d need block self\n", xthread_current->tid);
        xthread_semaphore_wait_inneral(sema);
    }
}

int xthread_sema_try_wait(xthread_sema_t *sema)
{
    xthread_mutex_lock(&sema->lock);
    if (sema->counter > 0) {
		sema->counter--;
    } else {
        xthread_mutex_unlock(&sema->lock);
		return -1;
	}
    xthread_mutex_unlock(&sema->lock);	
    return 0;
}

void xthread_sema_post(xthread_sema_t *sema)
{
    XPRINT("sema post %d\n", xthread_current->tid);
    xthread_mutex_lock(&sema->lock);
    if (list_empty(&sema->waiters.wait_list)) { 	
        XPRINT("sema post %d ok\n", xthread_current->tid);
        sema->counter++;
        xthread_mutex_unlock(&sema->lock);
 	} else {
        XPRINT("sema post %d need wakeup others\n", xthread_current->tid);
        xthread_sema_post_inneral(sema);
	}
}

/**
 * 信号量wait超时
 * 超时则返回ETIMEOUT, 没有则返回0
 */
int xthread_sema_wait_timeout(xthread_sema_t *sema, struct timeval *tv)
{
    if (!tv) {
        xthread_sema_wait(sema);
        return 0;
    }
    unsigned long abs_ms = tv->tv_sec * 1000;
    abs_ms += tv->tv_usec / 1000;

    struct timeval tv0;
    while (1) {
        gettimeofday(&tv0, NULL);
        unsigned long new_ms = tv0.tv_sec * 1000;
        new_ms += tv0.tv_usec / 1000;
        if (new_ms > abs_ms) {
            // printf("new us:%d old:%d\n", new_ms, abs_ms);
            break;
        }
        if (!xthread_sema_try_wait(sema))
            return 0;
    }
    return ETIMEDOUT;
}

int xthread_sema_getvalue(xthread_sema_t *sema, int *sval)
{
    if (sema == NULL || sval == NULL) {
        return EINVAL;
    }
    *sval = sema->counter;
    return 0;
}
